Merged [21414]: When the message window opens in 10.5, the messages tabView starts...
[adiumx.git] / Plugins / Contact Status Coloring / AIContactStatusColoringPlugin.m
blobe9e7aa6923296ae145fd8d0363117b6a7c3f09d6
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "AIAbstractListController.h"
18 #import <Adium/AIContactControllerProtocol.h>
19 #import "AIContactStatusColoringPlugin.h"
20 #import <Adium/AIInterfaceControllerProtocol.h>
21 #import <Adium/AIPreferenceControllerProtocol.h>
22 #import "AIListThemeWindowController.h"
23 #import <AIUtilities/AIColorAdditions.h>
24 #import <AIUtilities/AIDictionaryAdditions.h>
25 #import <AIUtilities/AIMutableOwnerArray.h>
26 #import <Adium/AIListContact.h>
27 #import <Adium/AIChat.h>
28 #import <Adium/AIContentTyping.h>
29 #import <Adium/AIListObject.h>
30 #import <Adium/AIMetaContact.h>
32 @interface AIContactStatusColoringPlugin (PRIVATE)
33 - (void)addToFlashSet:(AIListObject *)inObject;
34 - (void)removeFromFlashSet:(AIListObject *)inObject;
35 - (void)preferencesChanged:(NSNotification *)notification;
36 - (void)_applyColorToContact:(AIListContact *)inObject;
37 @end
39 @implementation AIContactStatusColoringPlugin
41 #define OFFLINE_IMAGE_OPACITY   0.5
42 #define FULL_IMAGE_OPACITY              1.0
43 #define OPACITY_REFRESH                 0.2
45 #define CONTACT_STATUS_COLORING_DEFAULT_PREFS   @"ContactStatusColoringDefaults"
47 - (void)installPlugin
49     //init
50     flashingListObjects = [[NSMutableSet alloc] init];
51     awayColor = nil;
52     idleColor = nil;
53     signedOffColor = nil;
54     signedOnColor = nil;
55     typingColor = nil;
56     unviewedContentColor = nil;
57     onlineColor = nil;
58     awayAndIdleColor = nil;
59         offlineColor = nil;
60         
61     awayInvertedColor = nil;
62     idleInvertedColor = nil;
63     signedOffInvertedColor = nil;
64     signedOnInvertedColor = nil;
65     typingInvertedColor = nil;
66     unviewedContentInvertedColor = nil;
67     onlineInvertedColor = nil;
68     awayAndIdleInvertedColor = nil;
69         offlineInvertedColor = nil;
70         
71     awayLabelColor = nil;
72     idleLabelColor = nil;
73     signedOffLabelColor = nil;
74     signedOnLabelColor = nil;
75     typingLabelColor = nil;
76     unviewedContentLabelColor = nil;
77     onlineLabelColor = nil;
78     awayAndIdleLabelColor = nil;
79         offlineLabelColor = nil;
80         
81         offlineImageFading = NO;
82         
83         id<AIPreferenceController> preferenceController = [adium preferenceController];
85     //Setup our preferences
86     [preferenceController registerDefaults:[NSDictionary dictionaryNamed:CONTACT_STATUS_COLORING_DEFAULT_PREFS
87                                                                                                                                                 forClass:[self class]]
88                                                                                   forGroup:PREF_GROUP_LIST_THEME];
89     
90     //Observe preferences and list objects
91         [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_LIST_THEME];
92         [preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_CONTACT_LIST];
93         [[adium contactController] registerListObjectObserver:self];
96 - (void)uninstallPlugin
98         [[adium preferenceController] unregisterPreferenceObserver:self];
99         [[adium    contactController] unregisterListObjectObserver:self];
100         [[adium  interfaceController] unregisterFlashObserver:self];    
103 - (void)dealloc
105         [flashingListObjects release]; flashingListObjects = nil;
107         [super dealloc];
111 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
113     NSSet               *modifiedAttributes = nil;
115         if ([inObject isKindOfClass:[AIListContact class]]) {
116                 if (    inModifiedKeys == nil ||
117                         [inModifiedKeys containsObject:KEY_TYPING] ||
118                         [inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT] || 
119                         [inModifiedKeys containsObject:@"StatusType"] ||
120                         [inModifiedKeys containsObject:@"IsIdle"] ||
121                         [inModifiedKeys containsObject:@"Online"] ||
122                         [inModifiedKeys containsObject:@"Signed On"] || 
123                         [inModifiedKeys containsObject:@"Signed Off"]) {
125                         //Update the contact's text color
126                         [self _applyColorToContact:(AIListContact *)inObject];
127                         modifiedAttributes = [NSSet setWithObjects:@"Text Color", @"Inverted Text Color", @"Label Color", nil];
128                 }
129                 
130                 //Update our flash set
131                 if ((inModifiedKeys == nil || [inModifiedKeys containsObject:KEY_UNVIEWED_CONTENT]) && 
132                    flashUnviewedContentEnabled) {
133                         int unviewedContent = [inObject integerStatusObjectForKey:KEY_UNVIEWED_CONTENT];
134                         
135                         if (unviewedContent && ![flashingListObjects containsObject:inObject]) { //Start flashing
136                                 [self addToFlashSet:inObject];
137                         } else if (!unviewedContent && [flashingListObjects containsObject:inObject]) { //Stop flashing
138                                 [self removeFromFlashSet:inObject];
139                         }
140                 }
141         }
143     return modifiedAttributes;
146 //Applies the correct color to the passed object
147 - (void)_applyColorToContact:(AIListContact *)inContact
149     NSColor                     *color = nil, *invertedColor = nil, *labelColor = nil;
150     int                         unviewedContent, away;
151     int                         idle;
152         float                   opacity = FULL_IMAGE_OPACITY;
153         BOOL                    isEvent = NO;
155     //Prefetch the value for unviewed content, we need it multiple times below
156     unviewedContent = [inContact integerStatusObjectForKey:KEY_UNVIEWED_CONTENT];
158     //Unviewed content
159     if (!color && (unviewedContentEnabled && unviewedContent)) {
160                 /* Use the unviewed content settings if:
161                  *      - we aren't flashing or
162                  *  - every other flash. */
163         if (!flashUnviewedContentEnabled || ([[adium interfaceController] flashState] % 2)) {
164             color = unviewedContentColor;
165             invertedColor = unviewedContentInvertedColor;
166             labelColor = unviewedContentLabelColor;
167                         isEvent = YES;
168         }
169     }
171     //Offline, Signed off, signed on, or typing
172     if (!color/* && (!unviewedContentEnabled || !unviewedContent)*/) {
173                 if (offlineEnabled && (![inContact online] &&
174                                                           ![inContact integerStatusObjectForKey:@"Signed Off"])) {
175                         color = offlineColor;
176                         invertedColor = offlineInvertedColor;
177                         labelColor = offlineLabelColor;
178                         if (offlineImageFading) opacity = OFFLINE_IMAGE_OPACITY;                        
179                         
180                 } else if (signedOffEnabled && ([inContact integerStatusObjectForKey:@"Signed Off"])) {
182                         //Set colors
183             color = signedOffColor;
184             invertedColor = signedOffInvertedColor;
185             labelColor = signedOffLabelColor;
186                         isEvent = YES;
188         } else if (signedOnEnabled && [inContact integerStatusObjectForKey:@"Signed On"]) {
189             
190                         color = signedOnColor;
191             invertedColor = signedOnInvertedColor;
192             labelColor = signedOnLabelColor;
193                         isEvent = YES;
194                         
195         } else if (typingEnabled && ([inContact integerStatusObjectForKey:KEY_TYPING] == AITyping)) {
196             color = typingColor;
197             invertedColor = typingInvertedColor;
198             labelColor = typingLabelColor;
199                         isEvent = YES;
200                         
201         }
202     }
204     if (!color) {
205                 AIStatusSummary statusSummary = [inContact statusSummary];
207         //Prefetch these values, we need them multiple times below
208         away = [inContact integerStatusObjectForKey:@"Away" fromAnyContainedObject:NO];
209         idle = [inContact integerStatusObjectForKey:@"Idle" fromAnyContainedObject:NO];
211         //Idle And Away, Away, or Idle
212         if (awayAndIdleEnabled && (statusSummary == AIAwayAndIdleStatus)) {
213             color = awayAndIdleColor;
214             invertedColor = awayAndIdleInvertedColor;
215             labelColor = awayAndIdleLabelColor;
216         } else if (awayEnabled && ((statusSummary == AIAwayStatus) || (statusSummary == AIAwayAndIdleStatus))) {
217             color = awayColor;
218             invertedColor = awayInvertedColor;
219             labelColor = awayLabelColor;
220         } else if (idleEnabled && ((statusSummary == AIIdleStatus) || (statusSummary == AIAwayAndIdleStatus))) {
221             color = idleColor;
222             invertedColor = idleInvertedColor;
223             labelColor = idleLabelColor;
224         }
225     }
227     //Online
228     if (!color && onlineEnabled && [inContact online]) {
229         color = onlineColor;
230         invertedColor = onlineInvertedColor;
231         labelColor = onlineLabelColor;
232     }
234     //Apply the color and opacity
235     [[inContact displayArrayForKey:@"Text Color"] setObject:color withOwner:self];
236     [[inContact displayArrayForKey:@"Inverted Text Color"] setObject:invertedColor withOwner:self];
237     [[inContact displayArrayForKey:@"Label Color"] setObject:labelColor withOwner:self];
238         [[inContact displayArrayForKey:@"Image Opacity"] setObject:[NSNumber numberWithFloat:opacity] withOwner:self];
239         [[inContact displayArrayForKey:@"Is Event"] setObject:[NSNumber numberWithBool:isEvent] withOwner:self];
242 //Flash all handles with unviewed content
243 - (void)flash:(int)value
245     NSEnumerator        *enumerator;
246     AIListContact       *object;
248     enumerator = [flashingListObjects objectEnumerator];
249     while ((object = [enumerator nextObject])) {
250         [self _applyColorToContact:object];
251         
252         //Force a redraw
253         [[adium notificationCenter] postNotificationName:ListObject_AttributesChanged 
254                                                                                                   object:object
255                                                                                                 userInfo:[NSDictionary dictionaryWithObject:[NSArray arrayWithObjects:@"Text Color", @"Label Color", @"Inverted Text Color", nil] forKey:@"Keys"]];
256     }
260  * @brief Add a handle to the flash set
261  */
262 - (void)addToFlashSet:(AIListObject *)inObject
264     //Ensure that we're observing the flashing
265     if ([flashingListObjects count] == 0) {
266         [[adium interfaceController] registerFlashObserver:self];
267     }
269     //Add the contact to our flash set
270     [flashingListObjects addObject:inObject];
271     [self flash:[[adium interfaceController] flashState]];
275  * @brief Remove a contact from the flash set
276  */
277 - (void)removeFromFlashSet:(AIListObject *)inObject
279     //Remove the contact from our flash set
280     [flashingListObjects removeObject:inObject];
282     //If we have no more flashing contacts, stop observing the flashes
283     if ([flashingListObjects count] == 0) {
284         [[adium interfaceController] unregisterFlashObserver:self];
285     }
289 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
290                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
292         if ([group isEqualToString:PREF_GROUP_LIST_THEME]) {
293                 //Release the old values..
294                 [signedOffColor release];
295                 [signedOnColor release];
296                 [awayColor release];
297                 [idleColor release];
298                 [typingColor release];
299                 [unviewedContentColor release];
300                 [onlineColor release];
301                 [awayAndIdleColor release];
302                 [offlineColor release];
303                 
304                 [signedOffInvertedColor release];
305                 [signedOnInvertedColor release];
306                 [awayInvertedColor release];
307                 [idleInvertedColor release];
308                 [typingInvertedColor release];
309                 [unviewedContentInvertedColor release];
310                 [onlineInvertedColor release];
311                 [awayAndIdleInvertedColor release];
312                 [offlineInvertedColor release];
313                 
314                 [awayLabelColor release];
315                 [idleLabelColor release];
316                 [signedOffLabelColor release];
317                 [signedOnLabelColor release];
318                 [typingLabelColor release];
319                 [unviewedContentLabelColor release];
320                 [onlineLabelColor release];
321                 [awayAndIdleLabelColor release];
322                 [offlineLabelColor release];
323                 
324                 //
325                 offlineImageFading = [[prefDict objectForKey:KEY_LIST_THEME_FADE_OFFLINE_IMAGES] boolValue];
326                 
327                 //Cache the preference values
328                 signedOffColor = [[[prefDict objectForKey:KEY_SIGNED_OFF_COLOR] representedColor] retain];
329                 signedOnColor = [[[prefDict objectForKey:KEY_SIGNED_ON_COLOR] representedColor] retain];
330                 awayColor = [[[prefDict objectForKey:KEY_AWAY_COLOR] representedColor] retain];
331                 idleColor = [[[prefDict objectForKey:KEY_IDLE_COLOR] representedColor] retain];
332                 typingColor = [[[prefDict objectForKey:KEY_TYPING_COLOR] representedColor] retain];
333                 unviewedContentColor = [[[prefDict objectForKey:KEY_UNVIEWED_COLOR] representedColor] retain];
334                 onlineColor = [[[prefDict objectForKey:KEY_ONLINE_COLOR] representedColor] retain];
335                 awayAndIdleColor = [[[prefDict objectForKey:KEY_IDLE_AWAY_COLOR] representedColor] retain];
336                 offlineColor = [[[prefDict objectForKey:KEY_OFFLINE_COLOR] representedColor] retain];
337                 
338                 signedOffInvertedColor = [[signedOffColor colorWithInvertedLuminance] retain];
339                 signedOnInvertedColor = [[signedOnColor colorWithInvertedLuminance] retain];
340                 awayInvertedColor = [[awayColor colorWithInvertedLuminance] retain];
341                 idleInvertedColor = [[idleColor colorWithInvertedLuminance] retain];
342                 typingInvertedColor = [[typingColor colorWithInvertedLuminance] retain];
343                 unviewedContentInvertedColor = [[unviewedContentColor colorWithInvertedLuminance] retain];
344                 onlineInvertedColor = [[onlineColor colorWithInvertedLuminance] retain];
345                 awayAndIdleInvertedColor = [[awayAndIdleColor colorWithInvertedLuminance] retain];
346                 offlineInvertedColor = [[offlineColor colorWithInvertedLuminance] retain];
347                 
348                 awayLabelColor = [[[prefDict objectForKey:KEY_LABEL_AWAY_COLOR] representedColor] retain];
349                 idleLabelColor = [[[prefDict objectForKey:KEY_LABEL_IDLE_COLOR] representedColor] retain];
350                 signedOffLabelColor = [[[prefDict objectForKey:KEY_LABEL_SIGNED_OFF_COLOR] representedColor] retain];
351                 signedOnLabelColor = [[[prefDict objectForKey:KEY_LABEL_SIGNED_ON_COLOR] representedColor] retain];
352                 typingLabelColor = [[[prefDict objectForKey:KEY_LABEL_TYPING_COLOR] representedColor] retain];
353                 unviewedContentLabelColor = [[[prefDict objectForKey:KEY_LABEL_UNVIEWED_COLOR] representedColor] retain];
354                 onlineLabelColor = [[[prefDict objectForKey:KEY_LABEL_ONLINE_COLOR] representedColor] retain];
355                 awayAndIdleLabelColor = [[[prefDict objectForKey:KEY_LABEL_IDLE_AWAY_COLOR] representedColor] retain];
356                 offlineLabelColor = [[[prefDict objectForKey:KEY_LABEL_OFFLINE_COLOR] representedColor] retain];
357                 
358                 //
359                 awayEnabled = [[prefDict objectForKey:KEY_AWAY_ENABLED] boolValue];
360                 idleEnabled = [[prefDict objectForKey:KEY_IDLE_ENABLED] boolValue];
361                 signedOffEnabled = [[prefDict objectForKey:KEY_SIGNED_OFF_ENABLED] boolValue];
362                 signedOnEnabled = [[prefDict objectForKey:KEY_SIGNED_ON_ENABLED] boolValue];
363                 typingEnabled = [[prefDict objectForKey:KEY_TYPING_ENABLED] boolValue];
364                 unviewedContentEnabled = [[prefDict objectForKey:KEY_UNVIEWED_ENABLED] boolValue];
365                 onlineEnabled = [[prefDict objectForKey:KEY_ONLINE_ENABLED] boolValue];
366                 awayAndIdleEnabled = [[prefDict objectForKey:KEY_IDLE_AWAY_ENABLED] boolValue];
367                 offlineEnabled = [[prefDict objectForKey:KEY_OFFLINE_ENABLED] boolValue];
368                 
369                 //Update all objects
370                 if (!firstTime) {
371                         [[adium contactController] updateAllListObjectsForObserver:self];
372                 }
374         } else if ([group isEqualToString:PREF_GROUP_CONTACT_LIST]) {
375                 BOOL oldFlashUnviewedContentEnabled = flashUnviewedContentEnabled;
376                 
377                 flashUnviewedContentEnabled = [[prefDict objectForKey:KEY_CL_FLASH_UNVIEWED_CONTENT] boolValue];
379                 if (oldFlashUnviewedContentEnabled && !flashUnviewedContentEnabled) {
380                         //Clear our flash set if we aren't flashing for unviewed content now but we were before
381                         NSEnumerator    *enumerator = [[[flashingListObjects copy] autorelease] objectEnumerator];
382                         AIListContact   *listContact;
384                         while ((listContact = [enumerator nextObject])) {
385                                 [self removeFromFlashSet:listContact];
386                         }
387                         
388                         //Make our colors end up right (if we were on an off-flash) by updating all list objects
389                         [[adium contactController] updateAllListObjectsForObserver:self];
390                 } else if (!oldFlashUnviewedContentEnabled && flashUnviewedContentEnabled) {
391                         if (!firstTime) {
392                                 //Update all list objects so we start flashing
393                                 [[adium contactController] updateAllListObjectsForObserver:self];
394                         }
395                 }
396         }
399 @end